package com.mrxu.monitor.service;

import com.mrxu.monitor.bean.*;
import com.mrxu.monitor.config.MonitorProperties;
import com.mrxu.monitor.constant.RedisConstant;
import com.mrxu.monitor.notice.MessageCenterService;
import com.mrxu.monitor.util.DateUtil;
import com.mrxu.monitor.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@Service
public class ReportService {

    @Autowired
    private MonitorProperties monitorProperties;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private MessageCenterService messageCenterService;

    @Autowired
    private IpService ipService;

    @Autowired
    private RequestUtil requestUtil;

    /**
     * 红绿切换的flag
     */
    private AtomicBoolean redGreenFlag = new AtomicBoolean();

    private DayCount redDayCount = new DayCount();

    private DayCount greenDayCount = new DayCount();

    public void startRequest(HttpServletRequest request) {
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setStartTime(System.currentTimeMillis());
        requestInfo.setOs(requestUtil.getOs(request));
        requestInfo.setBrowser(requestUtil.getBrowser(request));
        requestInfo.setIp(requestUtil.getClientIP(request));
        requestInfo.setPath(request.getRequestURI());
        requestInfo.setMobile(requestUtil.isMobile(request));
        request.setAttribute("requestInfo", requestInfo);

        if(redGreenFlag.get()) {
            redDayCount.startRequest(requestInfo);
        } else {
            greenDayCount.startRequest(requestInfo);
        }
    }

    public void endRequest(HttpServletRequest request, HttpServletResponse response) {
        RequestInfo requestInfo = (RequestInfo) request.getAttribute("requestInfo");
        requestInfo.setEndTime(System.currentTimeMillis());
        requestInfo.setStatus(response.getStatus());
        int contentLength = response.getBufferSize();
        requestInfo.setResponseSize(contentLength);

        if(redGreenFlag.get()) {
            redDayCount.endRequest(requestInfo);
        } else {
            greenDayCount.endRequest(requestInfo);
        }
    }

    public void addBusinessErrorCount() {
        if(redGreenFlag.get()) {
            redDayCount.getBusinessErrorCount().getAndIncrement();
        } else {
            greenDayCount.getBusinessErrorCount().getAndIncrement();
        }
    }


    /**
     * 获取hash的值
     * @param hashValues
     * @param mapKey
     * @param defaultValue
     * @return
     */
    private Long getOrDefaultHashValue(Map<Object,Object> hashValues,String mapKey,Long defaultValue) {
        if(hashValues == null) {
            return defaultValue;
        }
        Object value = hashValues.get(mapKey);
        if(value == null) {
            return defaultValue;
        }
        return Long.valueOf((String) value);
    }

    /**
     * 获取静态字段数据
     * @return 结果
     */
    public Statistics stastic() {
        String todayStr = DateUtil.getTodayStr();
        String redisKey = RedisConstant.getKey(RedisConstant.STASTIC,todayStr);
        Statistics report = new Statistics();
        Map<Object,Object> hashValues = redisTemplate.opsForHash().entries(redisKey);
        report.setPv(getOrDefaultHashValue(hashValues,"pv",0L));
        report.setUv(getOrDefaultHashValue(hashValues,"uv",0L));
        report.setMobileCount(getOrDefaultHashValue(hashValues,"mobile",0L));
        report.setPcCount(getOrDefaultHashValue(hashValues,"pc",0L));
        report.setExceptionCount(getOrDefaultHashValue(hashValues,"exception",0L));
        report.setAlarmCount(getOrDefaultHashValue(hashValues,"alarm",0L));
        report.setFlowSum(getOrDefaultHashValue(hashValues,"flow",0L));
        report.setBizErrorCount(getOrDefaultHashValue(hashValues,"bizCodeError",0L));
        String pathCountKey = RedisConstant.getKey(RedisConstant.PATHCOUNT,todayStr);
        report.setPathCount(redisTemplate.opsForZSet().size(pathCountKey));
        return report;
    }


    /**
     * 获取最近的pvuv数据
     * @param beforeDays
     * @return
     */
    public List<PvUv> pvUvHistory(int beforeDays) {
        List<PvUv> pvUvList = new ArrayList<>();
        for(int i=0;i<beforeDays;i++) {
            String dateStr = DateUtil.getTodayBeforeStr(beforeDays-i);
            String redisKey = RedisConstant.getKey(RedisConstant.STASTIC,dateStr);
            Map<Object,Object> hashValues = redisTemplate.opsForHash().entries(redisKey);
            PvUv pvUv = new PvUv();
            pvUv.setDate(dateStr);
            pvUv.setUv(getOrDefaultHashValue(hashValues,"uv",0L));
            pvUv.setPv(getOrDefaultHashValue(hashValues,"pv",0L));
            pvUvList.add(pvUv);
        }
        return pvUvList;
    }

    /**
     * 浏览器统计
     * @param topN topN
     * @return 浏览器统计结果
     */
    public List<Browser> browserTopN(int topN) {
        List<Browser> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.BROWSER,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,topN-1);
        if(topNInfo == null){
            return res;
        }
        Iterator<ZSetOperations.TypedTuple<String>> browIt = topNInfo.iterator();
        while (browIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = browIt.next();
            Browser b = new Browser();
            b.setName(tempInfo.getValue());
            b.setCount(tempInfo.getScore().longValue());
            res.add(b);
        }
        return res;
    }

    /**
     * 状态码统计
     * @return 状态码统计结果
     */
    public List<HttpStatus> httpStatus() {
        List<HttpStatus> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.HTTPSTATUS,DateUtil.getTodayStr());
        Map<Object,Object> mapValues = redisTemplate.opsForHash().entries(redisKey);
        Iterator keyIt = mapValues.keySet().iterator();
        while(keyIt.hasNext()){
            String status = (String) keyIt.next();
            Object value = mapValues.get(status);
            HttpStatus b = new HttpStatus();
            b.setStatus(status);
            b.setCount(Long.valueOf((String) value));
            res.add(b);
        }
        return res;
    }

    /**
     * topNProvince
     * @param topN topN
     * @return 结果
     */
    public List<ProvinceCity> topNCountry(int topN) {
        List<ProvinceCity> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.COUNTRY,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,topN-1);
        if(topNInfo == null){
            return res;
        }
        Iterator<ZSetOperations.TypedTuple<String>> countryIt = topNInfo.iterator();
        while (countryIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = countryIt.next();
            ProvinceCity b = new ProvinceCity();
            b.setName(tempInfo.getValue());
            b.setCount(tempInfo.getScore().longValue());
            res.add(b);
        }
        return res;
    }

    /**
     * topNProvince
     * @param topN topN
     * @return 结果
     */
    public List<ProvinceCity> topNProvince(int topN) {
        List<ProvinceCity> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.PROVINCE,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,topN-1);
        if(topNInfo == null){
            return res;
        }
        Iterator<ZSetOperations.TypedTuple<String>> provinceIt = topNInfo.iterator();
        while (provinceIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = provinceIt.next();
            ProvinceCity b = new ProvinceCity();
            b.setName(tempInfo.getValue());
            b.setCount(tempInfo.getScore().longValue());
            res.add(b);
        }
        return res;
    }

    /**
     * topnCity
     * @param topN topN
     * @return 结果
     */
    public List<ProvinceCity> topNCity(int topN) {
        List<ProvinceCity> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.CITY,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,topN-1);
        if(topNInfo==null){
            return res;
        }
        Iterator<ZSetOperations.TypedTuple<String>> cityIt = topNInfo.iterator();
        while (cityIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = cityIt.next();
            ProvinceCity b = new ProvinceCity();
            b.setName(tempInfo.getValue());
            b.setCount(tempInfo.getScore().longValue());
            res.add(b);
        }
        return res;
    }

    /**
     * topnPathCount
     * @param topN topN
     * @return 结果
     */
    public List<PathCount> pathCountTopN(int topN) {
        List<PathCount> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.PATHCOUNT,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,topN-1);
        if(topNInfo == null) {
            return res;
        }
        Iterator<ZSetOperations.TypedTuple<String>> pathIt = topNInfo.iterator();
        while (pathIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = pathIt.next();
            PathCount b = new PathCount();
            b.setPath(tempInfo.getValue());
            b.setCount(tempInfo.getScore().longValue());
            res.add(b);
        }
        return res;
    }

    /**
     * path路径耗时topn
     * @param topN
     * @return
     */
    public List<PathTime> pathTimeTopN(int topN) {
        List<PathTime> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.PATHTIME,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,topN-1);
        if(topNInfo==null){
            return res;
        }
        Iterator<ZSetOperations.TypedTuple<String>> pathIt = topNInfo.iterator();
        while (pathIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = pathIt.next();
            PathTime b = new PathTime();
            b.setPath(tempInfo.getValue());
            b.setTimeAvg(tempInfo.getScore().longValue());
            res.add(b);
        }
        return res;
    }

    /**
     * 攻击列表
     * @return 结果
     */
    public List<ExcepInfo> listExceptions() {
        List<ExcepInfo> res = new ArrayList<>();
        String redisKey = RedisConstant.getKey(RedisConstant.EXCEPTION,DateUtil.getTodayStr());
        Set<ZSetOperations.TypedTuple<String>> topNInfo = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey,0,30);
        if(topNInfo==null){
            return res;
        }
        DecimalFormat format = new DecimalFormat("#.0000000000");
        Iterator<ZSetOperations.TypedTuple<String>> attackIpIt = topNInfo.iterator();
        while (attackIpIt.hasNext()){
            ZSetOperations.TypedTuple<String> tempInfo = attackIpIt.next();
            String timeIpKey = tempInfo.getValue();
            String[] timeInfos = timeIpKey.split("_");
            double scoreInfo = tempInfo.getScore();
            long duration = 1;
            long count = 1;
            if(scoreInfo>0){
                count = (long) scoreInfo;
                duration = Long.parseLong(format.format(scoreInfo-count).replace(".",""));
            }
            ExcepInfo excepInfo = new ExcepInfo();
            excepInfo.setDuration(duration);
            excepInfo.setTime(timeInfos[0]);
            String message = timeInfos[1];
            if("bizCodeError".equalsIgnoreCase(message)){
                message = "业务类型错误码次数:" + count;
            }else if("httpStatusError".equalsIgnoreCase(message)) {
                message = "http接口错误码次数:" + count;
            }else if("httpTimeoutError".equalsIgnoreCase(message)) {
                message = "http接口慢响应次数:" + count;
            } else {
                IpRegion region = ipService.getRegionInfo(message);
                if(region.isChina()){
                    message = message + "(" + region.getProvince()+region.getCity() + ")";
                }else if(region.isUnkown()) {
                    message = "(未知)";
                }else if(region.isLocalIp()) {
                    message = "(本机)";
                }else{
                    message = message + "(" + region.getCountry() +")";
                }
                message = message + "访问" + count + "次";
            }
            excepInfo.setMessage(message);
            res.add(excepInfo);
        }
        if(!res.isEmpty()){
            res.sort(Comparator.comparing(ExcepInfo::getTime).reversed());
        }
        return res;
    }

    /**
     * 界面展示对象
     * @return 对象
     */
    public StatisticsInfo getReport() {
        StatisticsInfo res = new StatisticsInfo();
        res.setStatistics(stastic());
        res.setHttpStatusList(httpStatus());
        res.setBrowserTop4(browserTopN(4));
        res.setPathCountTop10(pathCountTopN(10));
        res.setPathTimeTop10(pathTimeTopN(10));
        res.setCountryTop(topNCountry(100));
        res.setProvinceTop5(topNProvince(5));
        res.setCityTop(topNCity(1000));
        res.setExcepInfoList(listExceptions());
        calWarning(res);
        return res;
    }

    // 计算是否正在告警
    private void calWarning(StatisticsInfo info)  {
        List<ExcepInfo> excepInfoList = info.getExcepInfoList();
        info.setWarning(false);
        info.setWarningMsg(null);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        for(ExcepInfo temp : excepInfoList) {
            try {
                Date date  = sdf.parse(temp.getTime());
                long time = date.getTime()+ temp.getDuration()*1000;
                // 5秒内有告警则提示
                if(System.currentTimeMillis()-time < 5*1000) {
                    info.setWarning(true);
                    info.setWarningMsg(temp.getMessage());
                    return;
                }
            }
            catch (Exception e) {

            }

        }
    }

    @Bean(initMethod="")
    public void timerTask() {
        if(monitorProperties.isEnable()) {
            //初始化信息
            redDayCount.initProperty(redisTemplate,ipService,messageCenterService,monitorProperties);
            greenDayCount.initProperty(redisTemplate,ipService,messageCenterService,monitorProperties);
            Timer timer = new Timer("定时写入监听缓存定时器",true);
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    writeDayCount2Redis();
                }
            },5*1000,1000);
        }
        else {
            log.warn("api 监控未开启！！！");
        }
    }

    /**
     * 定时任务写入中
     */
    private void writeDayCount2Redis(){
        try {
            if(redGreenFlag.get()) {
                greenDayCount.flush2Redis();
            } else {
                redDayCount.flush2Redis();
            }
            //设置相反的值
            redGreenFlag.set(!redGreenFlag.get());
        } catch (Exception e) {
            log.warn("写入监听失败",e);
        }
    }
}
